﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Numerics;

namespace Scheme.Net
{
    #region INumber interface
    public interface INumber
    {
        #region Properties
        bool IsExact { get; }
        int Sign { get; }
        bool IsZero { get; }
        bool IsOne { get; }
        #endregion

        #region Operators
        INumber Add(INumber n);
        INumber Subtract(INumber n);
        INumber Multiply(INumber n);
        INumber Divide(INumber n);
        INumber Negate();

        INumber Pow(INumber n);
        INumber Log(INumber b);
        INumber Sqrt();

        INumber Sin();
        INumber Cos();
        INumber Tan();
        INumber Sinh();
        INumber Cosh();
        INumber Tanh();
        INumber Asin();
        INumber Acos();
        INumber Atan();
        #endregion

        #region Exactitude
        INumber ToExact();
        INumber ToInexact();
        #endregion
    }
    #endregion

    #region Fixnum
    public struct Fixnum : IComparable, IComparable<Fixnum>, IComparable<Bignum>, IEquatable<Fixnum>
    {
        #region Representation
        private readonly int _value;
        public int Value { get { return _value; } }

        public override string ToString()
        {
            return Value.ToString(System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
        }
        #endregion

        #region Construction
        public Fixnum(int value) { this._value = value; }
        #endregion

        #region Coercion
        public static implicit operator Fixnum(byte n) { return new Fixnum(n); }
        public static implicit operator Fixnum(short n) { return new Fixnum(n); }
        public static implicit operator Fixnum(int n) { return new Fixnum(n); }

        public static implicit operator int(Fixnum n) { return n.Value; }
        public static implicit operator long(Fixnum n) { return n.Value; }
        public static implicit operator float(Fixnum n) { return n.Value; }
        public static implicit operator double(Fixnum n) { return n.Value; }

        public static implicit operator BigInteger(Fixnum n) { return n.Value; }
        public static implicit operator Bignum(Fixnum n) { return new Bignum(n.Value); }
        public static implicit operator Flonum(Fixnum n) { return new Flonum(n.Value); }
        public static implicit operator Rational(Fixnum n) { return new Rational(n, 1); }
        #endregion

        #region IComparable Members

        public int CompareTo(object obj)
        {
            if (obj is Bignum)
                return CompareTo((Bignum)obj);
            else if (obj is Fixnum)
                return CompareTo((Fixnum)obj);
            else
                throw new ArgumentException("Cannot compare Fixnum with this type.", "obj");
        }

        #endregion

        #region IComparable<Fixnum> Members

        public int CompareTo(Fixnum other)
        {
            return this.Value.CompareTo(other.Value);
        }

        #endregion

        #region IComparable<Bignum> Members

        public int CompareTo(Bignum other)
        {
            return -other.Value.CompareTo(this.Value);
        }

        #endregion

        #region IEquatable<Fixnum> Members

        public bool Equals(Fixnum other)
        {
            return this.Value == other.Value;
        }

        #endregion
    }
    #endregion

    #region Bignum
    public struct Bignum : IComparable, IComparable<Fixnum>, IComparable<Bignum>, IEquatable<Bignum>
    {
        #region Representation
        private readonly BigInteger _value;
        public BigInteger Value { get { return _value; } }

        public override string ToString()
        {
            return Value.ToString(System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
        }
        #endregion

        #region Construction
        public Bignum(BigInteger value) { this._value = value; }
        #endregion

        #region Coercion
        public static implicit operator Bignum(byte n) { return new Bignum(n); }
        public static implicit operator Bignum(short n) { return new Bignum(n); }
        public static implicit operator Bignum(int n) { return new Bignum(n); }
        public static implicit operator Bignum(long n) { return new Bignum(n); }
        public static implicit operator Bignum(BigInteger n) { return new Bignum(n); }

        public static explicit operator int(Bignum n) { return (int)n.Value; }
        public static explicit operator long(Bignum n) { return (long)n.Value; }
        public static explicit operator float(Bignum n) { return (float)n.Value; }
        public static explicit operator double(Bignum n) { return (double)n.Value; }

        public static implicit operator BigInteger(Bignum n) { return n.Value; }
        public static explicit operator Flonum(Bignum n) { return new Flonum((double)n.Value); }
        public static implicit operator Rational(Bignum n) { return new Rational(n, 1); }
        #endregion

        #region IComparable Members

        public int CompareTo(object obj)
        {
            if (obj is Bignum)
                return CompareTo((Bignum)obj);
            else if (obj is Fixnum)
                return CompareTo((Fixnum)obj);
            else
                throw new ArgumentException("Cannot compare Bignum with this type.", "obj");
        }

        #endregion

        #region IComparable<Bignum> Members

        public int CompareTo(Bignum other)
        {
            return this.Value.CompareTo(other.Value);
        }

        #endregion

        #region IComparable<Fixnum> Members

        public int CompareTo(Fixnum other)
        {
            return this.Value.CompareTo(other.Value);
        }

        #endregion

        #region IEquatable<Bignum> Members

        public bool Equals(Bignum other)
        {
            return this.Value == other.Value;
        }

        #endregion
    }
    #endregion

    #region Integer
    public struct Integer
        : INumber, IComparable, IComparable<Integer>, IComparable<Flonum>, IComparable<Rational>, IEquatable<Integer>
    {
        #region Representation
        private readonly bool _isBig;
        private readonly Fixnum _fnValue;
        private readonly Bignum _bnValue;

        public bool IsBig { get { return _isBig; } }
        public Fixnum FixValue { get { return _fnValue; } }
        public Bignum BigValue { get { return _bnValue; } }

        public override string ToString()
        {
            if (IsBig)
                return BigValue.ToString();
            else
                return FixValue.ToString();
        }
        #endregion

        #region Construction
        public Integer(int value) 
        { 
            this._fnValue = value;
            this._isBig = false;
            this._bnValue = 0;
        }
        public Integer(BigInteger value)
        {
            if (value > int.MaxValue || value < int.MinValue)
            {
                _bnValue = value;
                _fnValue = 0;
                _isBig = true;
            }
            else
            {
                _fnValue = (int)value;
                _bnValue = 0;
                _isBig = false;
            }
        }
        #endregion

        #region Coercion
        public static implicit operator Integer(byte n) { return new Integer(n); }
        public static implicit operator Integer(short n) { return new Integer(n); }
        public static implicit operator Integer(int n) { return new Integer(n); }
        public static implicit operator Integer(long n) { return new Integer(n); }
        public static implicit operator Integer(BigInteger n){ return new Integer(n); }
        public static implicit operator Integer(Fixnum n) { return new Integer(n.Value); }
        public static implicit operator Integer(Bignum n) { return new Integer(n.Value); }

        public static implicit operator BigInteger(Integer n) { return n.IsBig ? n.BigValue : n.FixValue; }
        public static explicit operator int(Integer n)
        {
            if (n.IsBig)
                throw new InvalidCastException();
            else
                return n.FixValue;
        }
        public static explicit operator Flonum(Integer n)
        {
            return n.IsBig ? new Flonum((double)n.BigValue) : new Flonum((double)n.FixValue);
        }
        public static implicit operator Rational(Integer n) { return new Rational(n, 1); }
        #endregion

        #region IComparable Members

        public int CompareTo(object obj)
        {
            if (obj is Integer)
                return CompareTo((Integer)obj);
            else if (obj is Flonum)
                return CompareTo((Flonum)obj);
            else if (obj is Rational)
                return CompareTo((Rational)obj);
            else
                throw new ArgumentException("Integer.CompareTo: Don't know how to compare these two numbers", "obj");
        }

        #endregion

        #region IComparable<Integer> Members

        public int CompareTo(Integer other)
        {
            if (this.IsBig)
                return this.BigValue.CompareTo(other);
            else
                return -this.FixValue.CompareTo(other);
        }

        #endregion

        #region IComparable<Flonum> Members

        public int CompareTo(Flonum other)
        {
            var f = (Flonum)this;
            if (f.Value > other.Value)
                return 1;
            else if (f.Value == other.Value)
                return 0;
            else
                return -1;
        }

        #endregion

        #region IComparable<Rational> Members

        public int CompareTo(Rational other)
        {
            return CompareTo((Flonum)other);
        }

        #endregion

        #region IEquatable<Integer> Members

        public bool Equals(Integer other)
        {
            if (this.IsBig != other.IsBig) return false;
            if (this.IsBig)
                return this.BigValue.Equals(other.BigValue);
            else
                return this.FixValue.Equals(other.FixValue);
        }

        #endregion

        #region INumber Members
        #region Properties
        public bool IsExact { get { return true; } }
        public int Sign
        {
            get
            {
                if (IsBig)
                    return BigValue.CompareTo(0);
                else
                    return FixValue.CompareTo(0);
            }
        }
        public bool IsZero { get { return this.Equals(0); } }
        public bool IsOne { get { return this.Equals(1); } }
        #endregion

        #region Basic Operators
        public INumber Add(INumber n)
        {
            // Dispatch on operand type
            if (n is Rational)
                return ((Rational)this).Add(n);
            else if (n is Flonum)
                return ((Flonum)this).Add(n);
            else if (n is Integer)
            {
                var n1 = this.IsBig ? this.BigValue : this.FixValue;
                var n2 = ((Integer)n).IsBig ? ((Integer)n).BigValue : ((Integer)n).FixValue;
                return new Integer(n1.Value + n2.Value);
            }
            else
                throw new ArgumentException("Integer.Add: operand is not an accepted numeric type", "n");
        }

        public INumber Subtract(INumber n)
        {
            // Dispatch on operand type
            if (n is Rational)
                return ((Rational)this).Subtract(n);
            else if (n is Flonum)
                return ((Flonum)this).Subtract(n);
            else if (n is Integer)
            {
                var n1 = this.IsBig ? this.BigValue : this.FixValue;
                var n2 = ((Integer)n).IsBig ? ((Integer)n).BigValue : ((Integer)n).FixValue;
                return new Integer(n1.Value - n2.Value);
            }
            else
                throw new ArgumentException("Integer.Subtract: operand is not an accepted numeric type", "n");
        }

        public INumber Multiply(INumber n)
        {
            // Dispatch on operand type
            if (n is Rational)
                return ((Rational)this).Multiply(n);
            else if (n is Flonum)
                return ((Flonum)this).Multiply(n);
            else if (n is Integer)
            {
                var n1 = this.IsBig ? this.BigValue : this.FixValue;
                var n2 = ((Integer)n).IsBig ? ((Integer)n).BigValue : ((Integer)n).FixValue;
                return new Integer(n1.Value * n2.Value);
            }
            else
                throw new ArgumentException("Integer.Multiply: operand is not an accepted numeric type", "n");
        }

        public INumber Divide(INumber n)
        {
            // Dispatch on operand type
            if (n is Rational)
                return ((Rational)this).Divide(n);
            else if (n is Flonum)
                return ((Flonum)this).Divide(n);
            else if (n is Integer)
                return new Rational(this, (Integer)n);
            else
                throw new ArgumentException("Integer.Divide: operand is not an accepted numeric type", "n");
        }

        public INumber Negate()
        {
            if (IsBig)
                return new Integer(-(BigInteger)this.BigValue);
            else
                return new Integer(-this.FixValue);
        }
        #endregion

        #region Exponentiation and Logarithms
        public INumber Pow(INumber n)
        {
            if (!this.IsBig)
            {
                if (n is Integer)
                {
                    var e = (Integer)n;
                    if (e.IsBig)
                        throw new ArgumentOutOfRangeException("n", "Integer.Pow: can only raise to 32-bit exponents.");

                    return new Flonum(Math.Pow((double)this.FixValue, e.FixValue));
                }
                else if (n is Rational || n is Flonum)
                {
                    var e = (Flonum)n;
                    return new Flonum(Math.Pow((double)this.FixValue, e.Value));
                }
                else
                    throw new ArgumentException("Integer.Pow: invalid exponent type", "n");
            }
            else
            {
                if (n is Integer && !((Integer)n).IsBig)
                {
                    var e = (Integer)n;
                    return new Integer(BigInteger.Pow(this.BigValue, e.FixValue));
                }
                else
                    throw new ArgumentOutOfRangeException("n", "Integer.Pow: bignums can only be raised to 32-bit integer exponents.");
            }
        }

        public INumber Log(INumber b)
        {
            if (b is Integer || b is Rational || b is Flonum)
            {
                var e = (Flonum)b;
                if (this.IsBig)
                    return new Flonum(BigInteger.Log(this.BigValue, e.Value));
                else
                    return new Flonum(Math.Pow((double)this.FixValue, e.Value));
            }
            else
                throw new ArgumentException("Integer.Log: invalid base type", "b");
        }

        public INumber Sqrt()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Sqrt: Cannot find square root of Bignums");

            if (this.FixValue < 0)
                return new Number(new Integer(0), new Flonum(Math.Sqrt((double)this.FixValue)));

            return new Flonum(Math.Sqrt((double)this.FixValue));
        }

        public Bignum SqrtDivRem(out Bignum rem)
        {
            BigInteger x = this;
            BigInteger res, bit;
            //The next line can be optimized using inner implementation of BigInteger
            for (bit = 4; bit < x; bit <<= 2) ; //4*bit>x
            for (res = 0; bit != 0; bit >>= 2)
                if (res + bit <= x) { x -= res + bit; res = (res >> 1) + bit; }
                else res >>= 1;
            rem = x;
            return res;
        }
        #endregion

        #region Trigonometric Functions
        public INumber Sin()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Sin: Cannot find square root of Bignums");
            return new Flonum(Math.Sin((double)this.FixValue));
        }

        public INumber Cos()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Cos: Cannot find square root of Bignums");
            return new Flonum(Math.Cos((double)this.FixValue));
        }

        public INumber Tan()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Tan: Cannot find square root of Bignums");
            return new Flonum(Math.Tan((double)this.FixValue));
        }

        public INumber Sinh()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Sinh: Cannot find square root of Bignums");
            return new Flonum(Math.Sinh((double)this.FixValue));
        }

        public INumber Cosh()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Cosh: Cannot find square root of Bignums");
            return new Flonum(Math.Cosh((double)this.FixValue));
        }

        public INumber Tanh()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Tanh: Cannot find square root of Bignums");
            return new Flonum(Math.Tanh((double)this.FixValue));
        }

        public INumber Asin()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Asin: Cannot find square root of Bignums");
            return new Flonum(Math.Asin((double)this.FixValue));
        }

        public INumber Acos()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Acos: Cannot find square root of Bignums");
            return new Flonum(Math.Acos((double)this.FixValue));
        }

        public INumber Atan()
        {
            if (this.IsBig)
                throw new InvalidOperationException("Integer.Atan: Cannot find square root of Bignums");
            return new Flonum(Math.Atan((double)this.FixValue));
        }
        #endregion
        #endregion

        #region Operators
        public static INumber operator +(Integer n1, INumber n2) { return n1.Add(n2); }
        public static INumber operator +(INumber n1, Integer n2) { return n1.Add(n2); }
        public static INumber operator -(Integer n1, INumber n2) { return n1.Subtract(n2); }
        public static INumber operator -(INumber n1, Integer n2) { return n1.Subtract(n2); }
        public static INumber operator *(Integer n1, INumber n2) { return n1.Multiply(n2); }
        public static INumber operator *(INumber n1, Integer n2) { return n1.Multiply(n2); }
        public static INumber operator /(Integer n1, INumber n2) { return n1.Divide(n2); }
        public static INumber operator /(INumber n1, Integer n2) { return n1.Divide(n2); }

        public static INumber operator -(Integer n) { return n.Negate(); }
        public static Integer operator ++(Integer n) { return (Integer)n.Add(new Integer(1)); }
        #endregion

        #region Exactitude

        public INumber ToExact()
        {
            return this;
        }

        public INumber ToInexact()
        {
            if (IsBig)
                return new Flonum((double)this.BigValue);
            else
                return new Flonum(this.FixValue);
        }

        #endregion
    }
    #endregion

    #region Rational
    public struct Rational
        : INumber, IComparable, IComparable<Integer>, IComparable<Flonum>, IComparable<Rational>, IEquatable<Rational>
    {
        #region Representation
        private readonly Integer _numerator;
        private readonly Integer _denominator;

        public Integer Numerator { get { return _numerator; } }
        public Integer Denominator { get { return _denominator; } }

        public override string ToString()
        {
            return string.Format("{0}/{1}", Numerator.ToString(), Denominator.ToString());
        }
        #endregion

        #region Construction
        public Rational(Integer numerator, Integer denominator)
        {
            if (denominator.IsZero)
                throw new ArgumentOutOfRangeException("denominator", "Cannot construct a rational with zero denominator");

            var gcd = BigInteger.GreatestCommonDivisor(numerator, denominator);
            var n = numerator / gcd; var d = denominator / gcd;
            if (d < 0 && n >= 0)
            {
                d = -d;
                n = -n;
            }

            this._numerator = n;
            this._denominator = d;
        }
        #endregion

        #region INumber Members
        #region Properties
        public bool IsExact { get { return true; } }
        public int Sign { get { return this.Numerator.CompareTo(0); } }
        public bool IsZero { get { return this.Numerator.Equals(0); } }
        public bool IsOne { get { return this.Numerator.Equals(this.Denominator); } }
        #endregion

        #region Basic Operators
        public INumber Add(INumber n)
        {
            if (n is Flonum)
                return ((Flonum)this).Add(n);
            else if (n is Rational)
            {
                var other = (Rational)n;
                var lcm = LeastCommonMultiple(this.Denominator, other.Denominator);
                var m1 = lcm / this.Denominator;
                var m2 = lcm / other.Denominator;

                return new Rational(m1 * this.Numerator + m2 * other.Numerator, m1 * this.Denominator);
            }

            throw new ArgumentException("Rational.Add: unexpected operand type", "n");
        }

        public INumber Subtract(INumber n)
        {
            if (n is Flonum)
                return ((Flonum)this).Subtract(n);
            else if (n is Rational)
            {
                var other = (Rational)n;
                var lcm = LeastCommonMultiple(this.Denominator, other.Denominator);
                var m1 = lcm / this.Denominator;
                var m2 = lcm / other.Denominator;

                return new Rational(m1 * this.Numerator - m2 * other.Numerator, m1 * this.Denominator);
            }

            throw new ArgumentException("Rational.Subtract: unexpected operand type", "n");
        }

        public INumber Multiply(INumber n)
        {
            if (n is Flonum)
                return ((Flonum)this).Multiply(n);
            else if (n is Rational)
            {
                var other = (Rational)n;
                INumber result = new Rational((Integer)this.Numerator.Multiply(other.Numerator), (Integer)this.Denominator.Multiply(other.Denominator));
                if (((Rational)result).Denominator.IsOne)
                    result = new Integer(((Rational)result).Numerator);
                return result;
            }
            else if (n is Integer)
            {
                var other = (Integer)n;
                INumber result = new Rational((Integer)this.Numerator.Multiply(other), (Integer)this.Denominator);
                if (((Rational)result).Denominator.IsOne)
                    result = new Integer(((Rational)result).Numerator);
                return result;
            }

            throw new ArgumentException("Rational.Multiply: unexpected operand type", "n");
        }

        public INumber Divide(INumber n)
        {
            if (n is Flonum)
                return ((Flonum)this).Divide(n);
            else if (n is Rational)
            {
                var other = (Rational)n;
                INumber result = new Rational((Integer)this.Numerator.Multiply(other.Denominator), (Integer)this.Denominator.Multiply(other.Numerator));
                if (((Rational)result).Denominator.IsOne)
                    result = new Integer(((Rational)result).Numerator);
                return result;
            }
            else if (n is Integer)
            {
                var other = (Integer)n;
                INumber result = new Rational((Integer)this.Numerator, (Integer)this.Denominator.Multiply(other));
                if (((Rational)result).Denominator.IsOne)
                    result = new Integer(((Rational)result).Numerator);
                return result;
            }

            throw new ArgumentException("Rational.Divide: unexpected operand type", "n");
        }

        public INumber Negate()
        {
            return new Rational((Integer)this.Numerator.Negate(), this.Denominator);
        }
        #endregion

        #region Exponentiation and Logarithms
        public INumber Pow(INumber n)
        {
            return ((Flonum)this).Pow(n);
        }

        public INumber Log(INumber b)
        {
            return ((Flonum)this).Log(b);
        }

        public INumber Sqrt()
        {
            return ((Flonum)this).Sqrt();
        }
        #endregion

        #region Trigonometric Functions
        public INumber Sin()
        {
            return ((Flonum)this).Sin();
        }

        public INumber Cos()
        {
            return ((Flonum)this).Cos();
        }

        public INumber Tan()
        {
            return ((Flonum)this).Tan();
        }

        public INumber Sinh()
        {
            return ((Flonum)this).Sinh();
        }

        public INumber Cosh()
        {
            return ((Flonum)this).Cosh();
        }

        public INumber Tanh()
        {
            return ((Flonum)this).Tanh();
        }

        public INumber Asin()
        {
            return ((Flonum)this).Asin();
        }

        public INumber Acos()
        {
            return ((Flonum)this).Acos();
        }

        public INumber Atan()
        {
            return ((Flonum)this).Atan();
        }
        #endregion
        #endregion

        #region IComparable Members

        public int CompareTo(object obj)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComparable<Integer> Members

        public int CompareTo(Integer other)
        {
            return CompareTo((Rational)other);
        }

        #endregion

        #region IComparable<Flonum> Members

        public int CompareTo(Flonum other)
        {
            return ((Flonum)this).CompareTo(other);
        }

        #endregion

        #region IComparable<Rational> Members

        private BigInteger LeastCommonMultiple(BigInteger a, BigInteger b)
        {
            return BigInteger.Abs(a * b) / BigInteger.GreatestCommonDivisor(a, b);
        }

        public int CompareTo(Rational other)
        {
            var lcm = LeastCommonMultiple(this.Denominator, other.Denominator);
            var m1 = lcm / this.Denominator;
            var m2 = lcm / other.Denominator;

            return (m1 * this.Numerator).CompareTo(m2 * other.Numerator);
        }

        #endregion

        #region IEquatable<Rational> Members

        public bool Equals(Rational other)
        {
            return this.Numerator.Equals(other.Numerator) && this.Denominator.Equals(other.Denominator);
        }

        #endregion

        #region Operators
        public static INumber operator +(Rational n1, INumber n2) { return n1.Add(n2); }
        public static INumber operator +(INumber n1, Rational n2) { return n1.Add(n2); }
        public static INumber operator -(Rational n1, INumber n2) { return n1.Subtract(n2); }
        public static INumber operator -(INumber n1, Rational n2) { return n1.Subtract(n2); }
        public static INumber operator *(Rational n1, INumber n2) { return n1.Multiply(n2); }
        public static INumber operator *(INumber n1, Rational n2) { return n1.Multiply(n2); }
        public static INumber operator /(Rational n1, INumber n2) { return n1.Divide(n2); }
        public static INumber operator /(INumber n1, Rational n2) { return n1.Divide(n2); }

        public static INumber operator -(Rational n) { return n.Negate(); }
        #endregion

        #region Exactitude

        public INumber ToExact()
        {
            return this;
        }

        public INumber ToInexact()
        {
            return (Flonum)this;
        }

        #endregion
    }
    #endregion

    #region Flonum
    public struct Flonum : INumber, IComparable, IComparable<Integer>, IComparable<Flonum>, IComparable<Rational>, IEquatable<Flonum>
    {
        #region Representation
        private readonly double _value;
        public double Value { get { return _value; } }

        public override string ToString()
        {
            return Value.ToString(System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
        }
        #endregion

        #region Construction
        public Flonum(double value) { this._value = value; }
        #endregion

        #region Coercion
        public static implicit operator double(Flonum f) { return f.Value; }

        public static implicit operator Flonum(Rational r)
        {
            if (!r.Numerator.IsBig && !r.Denominator.IsBig)
            {
                var n = (double)((int)r.Numerator);
                var d = (double)((int)r.Denominator);
                return new Flonum(n / d);
            }
            else
            {
                BigInteger n = r.Numerator;
                BigInteger d = r.Denominator;
                BigInteger rem = 0;
                var mod = BigInteger.DivRem(n, d, out rem);
                if (double.IsInfinity((double)mod))
                {
                    while (double.IsInfinity((double)mod))
                        mod /= 10;
                    return new Flonum((double)mod);
                }
                while (double.IsInfinity((double)rem) || double.IsInfinity((double)d))
                {
                    rem /= 10;
                    d /= 10;
                }

                return new Flonum((double)rem / (double)d);
            }
        }
        #endregion

        #region INumber Members
        #region Properties
        public bool IsExact { get { return false; } }
        public int Sign { get { return this.Value.CompareTo(0); } }
        public bool IsZero { get { return this.Value == 0.0; } }
        public bool IsOne { get { return this.Value == 1; } }
        #endregion

        #region Basic Operations
        public INumber Add(INumber n)
        {
            if (n is Integer || n is Rational || n is Flonum)
                return new Flonum(this.Value + (Flonum)n);

            throw new ArgumentException("Flonum.Add: unexpected operand type.", "n");
        }

        public INumber Subtract(INumber n)
        {
            if (n is Integer || n is Rational || n is Flonum)
                return new Flonum(this.Value - (Flonum)n);

            throw new ArgumentException("Flonum.Subtract: unexpected operand type.", "n");
        }

        public INumber Multiply(INumber n)
        {
            if (n is Integer || n is Rational || n is Flonum)
                return new Flonum(this.Value * (Flonum)n);

            throw new ArgumentException("Flonum.Multiply: unexpected operand type.", "n");
        }

        public INumber Divide(INumber n)
        {
            if (n is Integer || n is Rational || n is Flonum)
                return new Flonum(this.Value / (Flonum)n);

            throw new ArgumentException("Flonum.Divide: unexpected operand type.", "n");
        }

        public INumber Negate()
        {
            return new Flonum(-this.Value);
        }
        #endregion

        #region Exponentiation and Logarithms
        public INumber Pow(INumber n)
        {
            return new Flonum(Math.Pow(this, (Flonum)n));
        }

        public INumber Log(INumber b)
        {
            return new Flonum(Math.Log(this, (Flonum)b));
        }

        public INumber Sqrt()
        {
            if (this < 0)
                return new Number(new Integer(0), new Flonum(Math.Sqrt(this)));
            return new Flonum(Math.Sqrt(this));
        }
        #endregion

        #region Trigonometric Functions
        public INumber Sin()
        {
            return new Flonum(Math.Sin(this));
        }

        public INumber Cos()
        {
            return new Flonum(Math.Cos(this));
        }

        public INumber Tan()
        {
            return new Flonum(Math.Tan(this));
        }

        public INumber Sinh()
        {
            return new Flonum(Math.Sinh(this));
        }

        public INumber Cosh()
        {
            return new Flonum(Math.Cosh(this));
        }

        public INumber Tanh()
        {
            return new Flonum(Math.Tanh(this));
        }

        public INumber Asin()
        {
            return new Flonum(Math.Asin(this));
        }

        public INumber Acos()
        {
            return new Flonum(Math.Acos(this));
        }

        public INumber Atan()
        {
            return new Flonum(Math.Atan(this));
        }
        #endregion
        #endregion

        #region IComparable Members

        public int CompareTo(object obj)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComparable<Integer> Members

        public int CompareTo(Integer other)
        {
            return this.Value.CompareTo((Flonum)other);
        }

        #endregion

        #region IComparable<Flonum> Members

        public int CompareTo(Flonum other)
        {
            return this.Value.CompareTo(other.Value);
        }

        #endregion

        #region IComparable<Rational> Members

        public int CompareTo(Rational other)
        {
            return this.Value.CompareTo((Flonum)other);
        }

        #endregion

        #region IEquatable<Flonum> Members

        public bool Equals(Flonum other)
        {
            return this.Value.Equals(other.Value);
        }

        #endregion

        #region Operators
        public static INumber operator +(Flonum n1, INumber n2) { return n1.Add(n2); }
        public static INumber operator +(INumber n1, Flonum n2) { return n1.Add(n2); }
        public static INumber operator -(Flonum n1, INumber n2) { return n1.Subtract(n2); }
        public static INumber operator -(INumber n1, Flonum n2) { return n1.Subtract(n2); }
        public static INumber operator *(Flonum n1, INumber n2) { return n1.Multiply(n2); }
        public static INumber operator *(INumber n1, Flonum n2) { return n1.Multiply(n2); }
        public static INumber operator /(Flonum n1, INumber n2) { return n1.Divide(n2); }
        public static INumber operator /(INumber n1, Flonum n2) { return n1.Divide(n2); }

        public static INumber operator -(Flonum n) { return n.Negate(); }
        public static Flonum operator ++(Flonum n) { return (Flonum)n.Add(new Integer(1)); }
        #endregion

        #region Exactitude

        public INumber ToExact()
        {
            var x = this.Value;
            var startx = this.Value;
            var m = new long[2, 2];
            long maxden = (long)1e10;
            long ai;

            /* initialize matrix */
            m[0, 0] = m[1, 1] = 1;
            m[0, 1] = m[1, 0] = 0;

            /* loop finding terms until denom gets too big */
            while (m[1, 0] * (ai = (long)x) + m[1, 1] <= maxden)
            {
                long t;
                t = m[0, 0] * ai + m[0, 1];
                m[0, 1] = m[0, 0];
                m[0, 0] = t;
                t = m[1, 0] * ai + m[1, 1];
                m[1, 1] = m[1, 0];
                m[1, 0] = t;
                if (x == (double)ai) break;     // AF: division by zero
                x = 1 / (x - (double)ai);
                if (x > (double)0x7FFFFFFF) break;  // AF: representation failure
            }

            var n1 = m[0, 0];
            var d1 = m[1, 0];
            var e1 = startx - ((double)m[0, 0] / (double)m[1, 0]);

            ai = (maxden - m[1, 1]) / m[1, 0];
            m[0, 0] = m[0, 0] * ai + m[0, 1];
            m[1, 0] = m[1, 0] * ai + m[1, 1];

            var n2 = m[0, 0];
            var d2 = m[1, 0];
            var e2 = startx - ((double)m[0, 0] / (double)m[1, 0]);

            if (Math.Abs(e1) < Math.Abs(e2))
                return new Rational((Integer)n1, (Integer)d1);
            else
                return new Rational((Integer)n2, (Integer)d2);
        }

        public INumber ToInexact()
        {
            return this;
        }

        #endregion
    }
    #endregion

    #region Number
    public class Number
        : INumber, IComparable, IComparable<Number>, IComparable<Integer>, IComparable<Rational>, IComparable<Flonum>, IEquatable<Number>
    {
        #region Representation
        private readonly INumber _re;
        private readonly INumber _im;

        public INumber Re { get { return _re; } }
        public INumber Im { get { return _im; } }

        public override string ToString()
        {
            if (Im.IsZero)
                return Re.ToString();

            if (((Integer)0).CompareTo(Im) > 0)
                return string.Format("{0}{1}i", Re.ToString(), Im.ToString());

            return string.Format("{0}+{1}i", Re.ToString(), Im.ToString());
        }
        #endregion

        #region Construction
        public Number()
        {
            this._re = new Integer(0);
            this._im = new Integer(0);
        }

        public Number(INumber re)
        {
            if (re is Number)
                throw new ArgumentException("Number._ctor: cannot construct a complex number with complex parts", "re");

            this._re = re;
            this._im = new Integer(0);
        }

        public Number(INumber re, INumber im)
        {
            if (re is Number)
                throw new ArgumentException("Number._ctor: cannot construct a complex number with complex parts", "re");
            if (im is Number)
                throw new ArgumentException("Number._ctor: cannot construct a complex number with complex parts", "im");

            this._re = re;
            this._im = im;
        }
        #endregion

        #region Coercion
        public static implicit operator Number(byte n) { return new Number(new Integer(n)); }
        public static implicit operator Number(short n) { return new Number(new Integer(n)); }
        public static implicit operator Number(int n) { return new Number(new Integer(n)); }
        public static implicit operator Number(long n) { return new Number(new Integer(n)); }
        public static implicit operator Number(BigInteger n) { return new Number(new Integer(n)); }

        public static implicit operator Number(double n) { return new Number(new Flonum(n)); }
        public static implicit operator Number(Rational n) { return new Number(n); }
        public static implicit operator Number(Integer n) { return new Number(n); }
        public static implicit operator Number(Flonum n) { return new Number(n); }
        #endregion

        #region Parsing
        public static Rational ParseRational(string s)
        {
            var parts = s.Split('/');
            var n = BigInteger.Parse(parts[0], System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
            var d = BigInteger.Parse(parts[1], System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
            return new Rational(n, d);
        }

        public static Integer ParseInteger(string s)
        {
            return new Integer(BigInteger.Parse(s, System.Globalization.CultureInfo.InvariantCulture.NumberFormat));
        }

        public static Flonum ParseFlonum(string s)
        {
            return new Flonum(double.Parse(s, System.Globalization.CultureInfo.InvariantCulture.NumberFormat));
        }

        public static INumber Parse(string s)
        {
            string removedSign = "";
            if (s.StartsWith("-") || s.StartsWith("+"))
            {
                removedSign = s[0].ToString();
                s = s.Substring(1);
            }
            var parts = s.ToUpper().Split('+', '-');
            parts[0] = removedSign + parts[0];
            if (parts.Count() == 2)
            {
                INumber rePart;
                INumber imPart;
                if (parts[0].Contains('/'))
                    rePart = ParseRational(parts[0]);
                else if (parts[0].Contains('.'))
                    rePart = ParseFlonum(parts[0]);
                else
                    rePart = ParseInteger(parts[0]);

                if (parts[1].Contains('/'))
                    imPart = ParseRational(parts[1]);
                else if (parts[1].Contains('.'))
                    imPart = ParseFlonum(parts[1]);
                else
                    imPart = ParseInteger(parts[1]);

                return new Number(rePart, imPart);
            }
            else if (parts.Count() == 1)
            {
                if (parts[0].Contains('/'))
                    return ParseRational(parts[0]);
                else if (parts[0].Contains('.'))
                    return ParseFlonum(parts[0]);
                else
                    return ParseInteger(parts[0]);
            }
            else
                throw new ArgumentException(string.Format("Number.Parse: malformed numeric literal: {0}", s), "s");
        }
        #endregion

        #region IComparable Members

        public int CompareTo(object obj)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComparable<Number> Members

        public int CompareTo(Number other)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComparable<Integer> Members

        public int CompareTo(Integer other)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComparable<Rational> Members

        public int CompareTo(Rational other)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComparable<Flonum> Members

        public int CompareTo(Flonum other)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IEquatable<Number> Members

        public bool Equals(Number other)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region INumber Members
        #region Properties
        public bool IsExact { get { return Re.IsExact && Im.IsExact; } }
        public int Sign
        {
            get 
            {
                if (Re.IsZero)
                    return Im.Sign;
                else
                    return Re.Sign;
            }
        }
        public bool IsZero { get { return Re.IsZero && Im.IsZero; } }
        public bool IsOne { get { return Re.IsOne && Im.IsZero; } }

        #region Specific to Complex Numbers
        public INumber Magnitude { get { return Re.Pow(new Integer(2)).Add(Im.Pow(new Integer(2))).Sqrt(); } }
        public INumber Angle { get { return Im.Divide(Re).Atan(); } }
        #endregion
        #endregion

        #region Basic Operators
        public INumber Add(INumber n)
        {
            if (n is Number)
            {
                var other = (Number)n;
                return new Number(this.Re.Add(other.Re), this.Im.Add(other.Im));
            }
            else
                return new Number(this.Re.Add(n), this.Im);
        }

        public INumber Subtract(INumber n)
        {
            if (n is Number)
            {
                var other = (Number)n;
                return new Number(this.Re.Subtract(other.Re), this.Im.Subtract(other.Im));
            }
            else
                return new Number(this.Re.Subtract(n), this.Im);
        }

        public INumber Multiply(INumber n)
        {
            if (n is Number)
            {
                // (a + bi)*(c + di) = (ac - bd) + (bc + ad)i
                var other = (Number)n;
                var rePart = Re.Multiply(other.Re).Subtract(Im.Multiply(other.Im));
                var imPart = Im.Multiply(other.Re).Add(Re.Multiply(other.Im));
                return new Number(rePart, imPart);
            }
            else
                return new Number(this.Re.Multiply(n), this.Im.Multiply(n));
        }

        public INumber Divide(INumber n)
        {
            if (n is Number)
            {
                // (a + bi)*(c + di) = [(ac + bd)/(c^2 + d^2)] + [(bc - ad)/(c^2 + d^2)]i
                var other = (Number)n;
                var denom = other.Re.Multiply(other.Re).Add(other.Im.Multiply(other.Im));
                var rePart = Re.Multiply(other.Re).Add(Im.Multiply(other.Im));
                var imPart = Im.Multiply(other.Re).Subtract(Re.Multiply(other.Im));
                return new Number(rePart.Divide(denom), imPart.Divide(denom));
            }
            else
                return new Number(this.Re.Divide(n), this.Im.Divide(n));
        }

        public INumber Negate()
        {
            return new Number(this.Re.Negate(), this.Im.Negate());
        }
        #endregion

        #region Exponentiation and Logarithms
        public INumber Pow(INumber n)
        {
            var mag = this.Magnitude.Pow(n);
            var rePart = mag.Multiply(n.Multiply(Angle.Cos()));
            var imPart = mag.Multiply(n.Multiply(Angle.Sin()));
            return new Number(rePart, imPart);
        }

        public INumber Log(INumber b)
        {
            return new Number(this.Magnitude.Log(new Flonum(Math.E)), this.Angle);
        }

        public INumber Sqrt()
        {
            var mag = this.Magnitude.Sqrt();
            var rePart = mag.Multiply(Angle.Cos().Divide(new Integer(2)));
            var imPart = mag.Multiply(Angle.Sin().Divide(new Integer(2)));
            return new Number(rePart, imPart);
        }
        #endregion

        #region Trigonometric Functions
        public INumber Sin()
        {
            throw new NotImplementedException();
        }

        public INumber Cos()
        {
            throw new NotImplementedException();
        }

        public INumber Tan()
        {
            throw new NotImplementedException();
        }

        public INumber Sinh()
        {
            throw new NotImplementedException();
        }

        public INumber Cosh()
        {
            throw new NotImplementedException();
        }

        public INumber Tanh()
        {
            throw new NotImplementedException();
        }

        public INumber Asin()
        {
            throw new NotImplementedException();
        }

        public INumber Acos()
        {
            throw new NotImplementedException();
        }

        public INumber Atan()
        {
            throw new NotImplementedException();
        }
        #endregion
        #endregion

        #region Exactitude

        public INumber ToExact()
        {
            if (IsExact)
                return this;
            else
                return new Number(Re.ToExact(), Im.ToExact());
        }

        public INumber ToInexact()
        {
            if (!IsExact)
                return this;
            else
                return new Number(Re.ToInexact(), Im.ToInexact());
        }

        #endregion
    }
    #endregion
}
